package com.loser.excel.core;

import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.annotation.Excel;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.handler.inter.IExcelExportServer;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.loser.excel.config.ExcelExportProperties;
import com.loser.excel.constant.CMSExcelExportException;
import com.loser.excel.constant.ConstExecutor;
import com.loser.excel.constant.ExportStatusEnum;
import com.loser.excel.core.handler.ExcelExportHandler;
import com.loser.excel.core.handler.upload.UploadFileHandler;
import com.loser.excel.model.DemoExportBean;
import com.loser.excel.utils.ExceptionUtils;
import com.loser.redis.core.RedisHandler;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.Callable;


/**
 * cms easyPoi excel 导出服务
 *
 * @author alan
 * @date 2022/6/10 12:07
 */
public class ExcelExportServer {

    private static final String extName = ".xlsx";

    private static final Logger log = LoggerFactory.getLogger(ExcelExportServer.class);

    private final CmsExcelService server;
    private final RedisHandler redisHandler;
    private final UploadFileHandler uploadFileHandler;
    private final ExcelExportHandler excelExportHandler;
    private final ExcelExportProperties excelExportProperties;

    public ExcelExportServer(RedisHandler redisHandler, ExcelExportHandler excelExportHandler, CmsExcelService server, ExcelExportProperties excelExportProperties, UploadFileHandler uploadFileHandler) {
        this.redisHandler = redisHandler;
        this.excelExportHandler = excelExportHandler;
        this.server = server;
        this.excelExportProperties = excelExportProperties;
        this.uploadFileHandler = uploadFileHandler;
    }

    /**
     * 对导出请求的参数进行base64加密确定唯一请求
     *
     * @param type   导出接口的类型
     * @param params 限制参数
     * @see ExcelExportType type 对应的值
     */
    public String limitExportByParams(String type, Object... params) {

        if (ArrayUtil.isEmpty(params)) {
            ExceptionUtils.fail("参数错误");
        }
        StringBuilder sb = new StringBuilder();
        sb.append(type);
        for (Object param : params) {
            sb.append(JSON.toJSONString(param));
        }
        return "EXPORT:" + Base64.encode(sb.toString());

    }

    /**
     * 重载
     */
    public Long exportBigExcel(String user, Object params, Class<?> exportBeanClass, Callable<Long> countServer, CmsSearchExcelDataService exportServer, ExcelExportType type) {
        return exportBigExcel(ExcelExportParams.build(params, exportBeanClass, exportServer, user), type, countServer);
    }

    /**
     * 生成 excel 并上传 cos
     *
     * @param params          查询参数
     * @param excelExportType 导出对应的唯一枚举，一个导出接口对应一个
     * @see Excel  导出的实体类（需要添加注解）
     * 使用导出模板规则
     * 01 配置 常量
     * <p>
     * @see ExcelExportType
     * <p>
     * 02 编写参数实体（写子类）
     * @see ExcelExportParams
     * <p>
     * 03 添加导出的Bean
     * @see DemoExportBean
     * <p>
     * 04 添加查询数据的服务类（写实现类）
     * @see CmsSearchExcelDataService
     */
    private Long exportBigExcel(ExcelExportParams<?, ?> params, ExcelExportType excelExportType, Callable<Long> countServer) {

        checkParams(params, excelExportType);
        String encryptStr;
        Long taskId = null;
        String exportUser = params.getExportUser();
        try {
            Long exportCount = countServer.call();
            // 00 检测导出数量
            checkExportCount(exportCount, excelExportProperties.getMaxExportCount());

            // 01 初始化部分工单信息
            taskId = System.currentTimeMillis();
            params.setTaskId(taskId);
            params.setExportUser(exportUser);
            params.setExportCount(exportCount);
            encryptStr = limitExportByParams(excelExportType.getFileName(), exportUser);

            if (!redisHandler.lock(encryptStr, encryptStr, 3600000L)) {
                ExceptionUtils.fail("表格正在导出中，请稍等一会噢!!!");
            }

            String fileName = getFileName(excelExportType, exportUser);

            // 02 构建任务
            buildTaskInfo(taskId, exportUser, params, excelExportType, fileName);

            // 03 上传文件
            exportAndUpload(params, excelExportType, encryptStr, fileName, exportUser);

            return taskId;
        } catch (Exception e) {
            // 04 终止任务并更新任务为未知异常
            stopTaskByException(taskId, e);
            ExceptionUtils.fail(e);
        }

        return null;

    }

    private String getFileName(ExcelExportType excelExportType, String exportUser) {
        return excelExportType.getFileName() + "_" + DateUtil.format(new Date(), "yyyy_MM_dd_HH_ss_SS") + "_BY_" + exportUser + extName;
    }

    private void checkParams(ExcelExportParams params, ExcelExportType excelExportType) {

        if (Objects.isNull(params)) {
            ExceptionUtils.fail("导出参数为空");
        }
        if (Objects.isNull(excelExportType)) {
            ExceptionUtils.fail("导出接口枚举未定义");
        }
        if (Objects.isNull(params.getParams())) {
            ExceptionUtils.fail("导出自定义参数为空");
        }
        if (Objects.isNull(params.getExportBeanClass())) {
            ExceptionUtils.fail("导出实体未定义");
        }
        if (Objects.isNull(params.getExportServer())) {
            ExceptionUtils.fail("导出服务类不存在");
        }

        Field[] fields = params.getExportBeanClass().getDeclaredFields();
        boolean flag = true;
        for (Field field : fields) {
            Excel annotation = field.getAnnotation(Excel.class);
            if (Objects.nonNull(annotation)) {
                flag = false;
                break;
            }
        }
        if (flag) {
            ExceptionUtils.fail("导出实体缺少 Excel 注解");
        }

    }

    /**
     * 检测导出数量
     */
    private void checkExportCount(Long exportCount, Long count) {

        if (ObjectUtil.isNull(exportCount)) {
            return;
        }
        if (exportCount <= 0) {
            ExceptionUtils.fail("导出数据为空");
        }
        if (ObjectUtil.isNotNull(count) && exportCount > count) {
            ExceptionUtils.fail(String.format("据量超过限制条数(%s)，请添加筛选条件后重试", count));
        }

    }

    /**
     * 异步生成文件并上传
     */
    private void exportAndUpload(ExcelExportParams params, ExcelExportType excelExportType, String encryptStr, String fileName, String optUser) {

        long start = System.currentTimeMillis();
        Long taskId = params.getTaskId();

        ConstExecutor.EXECUTOR_GENERAL.execute(() -> {

            try {

                Class<?> exportBeanClass = params.getExportBeanClass();
                // 01 生成文件
                SXSSFWorkbook workbook = buildWorkbook(excelExportType.getTitleName(), excelExportType.getSheetName(), exportBeanClass, server, params);

                // 02 上传文件并更新任务下载地址
                uploadAndUpdateTask(workbook, fileName, params.getTaskId(), excelExportType.getCategory(), optUser);

            } catch (CMSExcelExportException e) {

                // 03 终止任务并更新任务状态为手动终止
                stopTaskByUser(taskId);
                log.info("exportBigExcel stopTaskByUser taskId:{} timeOut:{}", taskId, (System.currentTimeMillis() - start));

            } catch (Exception e) {

                log.error("exportBigExcel throw exception fileName:" + excelExportType.getFileName() + ",timeOut:" + (System.currentTimeMillis() - start), e);
                // 04 终止任务并更新任务为未知异常
                stopTaskByException(taskId, e);

            } finally {

                redisHandler.releaseLock(encryptStr, encryptStr);

            }

        });

    }

    /**
     * 异常中断任务
     */
    private void stopTaskByException(Long taskId, Exception e) {
        excelExportHandler.updateTaskStatus(taskId, ExportStatusEnum.FAILURE.getStatus(), e);
    }

    /**
     * 手动中断任务
     */
    private void stopTaskByUser(Long taskId) {
        excelExportHandler.updateTaskStatus(taskId, ExportStatusEnum.USER_STOP.getStatus(), null);
    }

    /**
     * 构建一个任务工单
     *
     * @param taskId     工单唯一id
     * @param exportUser 导出人
     * @param fileName
     */
    private void buildTaskInfo(Long taskId, String exportUser, ExcelExportParams params, ExcelExportType excelExportType, String fileName) {
        log.info("buildTaskInfo taskId:{}", taskId);
        excelExportHandler.initTaskInfoAndSave(taskId, exportUser, params, excelExportType, fileName);
    }

    /**
     * 上传文件后更新记录
     */
    private void uploadAndUpdateTask(SXSSFWorkbook workbook, String fileName, Long taskId, String category, String optUser) {

        boolean success = true;
        log.info("uploadAndUpdateTask taskId:{}", taskId);
        try {
            String path = uploadWordBook2cos(workbook, excelExportProperties.getTargetPath(), fileName, category, optUser);
            updateTaskPath(taskId, path);
        } catch (Exception exception) {
            excelExportHandler.updateTaskStatus(taskId, ExportStatusEnum.FAILURE.getStatus(), exception);
            success = false;
            log.error("uploadAndUpdateTask  error ex->", exception);
        }
        if (success) {
            log.info("uploadAndUpdateTask success taskId:{}", taskId);
        }

    }

    /**
     * 更新任务的文件下载地址
     */
    private void updateTaskPath(Long taskId, String path) {
        log.info("uploadAndUpdateTask taskId:{},path:{}", taskId, path);
        excelExportHandler.updateTaskPath(taskId, path);
    }

    /**
     * 构建一个excel workBook（使用后得关闭流）
     *
     * @param titleName   标题名
     * @param sheetName   表名
     * @param pojoClass   导出实体类（需要添加easyPoi的 @Excel）
     * @param server      查询数据的实现类（需要实现selectListForExcelExport方法）
     * @param queryParams 查询数据的参数
     */
    private SXSSFWorkbook buildWorkbook(String titleName, String sheetName, Class<?> pojoClass, IExcelExportServer server, Object queryParams) {

        ExportParams params = new ExportParams(titleName, sheetName);
        return (SXSSFWorkbook) ExcelExportUtil.exportBigExcel(params, pojoClass, server, queryParams);

    }

    /**
     * 提交excel文件到cos并返回文件在cos中对应的路径
     *
     * @param wb       工作簿
     * @param fileName 文件名称
     * @param optUser
     */
    private String uploadWordBook2cos(SXSSFWorkbook wb, String fileName, String path, String categoryType, String optUser) {
        return uploadFileHandler.uploadWordBook2cos(wb, path, fileName, categoryType, optUser);
    }


}
